#include <fs.h>
#include <string.h>

#define FS_MAGIC        "KFS-0.1"   // 文件系统名称&版本
#define ROOT_MAGIC      "root"      // 根目录名     

#define SEC_END_FLAG    ((U32)-1)   // 结束标志
#define MAP_UNIT_NUM    (SEC_SIZE/sizeof(U32)) // 一个扇区管理单元的大小

// 记录文件系统概要信息，存储于 0 号扇区
typedef struct FS_HEADER
{
    U08     forJmp[4];          // 预留 4 字节给跳转指令使用
    U08     magic[32];          // 文件系统名
    U32     sctNum;             // 文件系统可以使用的扇区总数
    U32     mapNum;             // 扇区分配表所占的扇区数
    U32     freeBegin;          // 我们把空闲扇区组织成一个链表，freeBegin 为第一个空闲的扇区号
    U32     freeNum;            // 空闲扇区数   
} FS_HEADER;

// 记录根目录相关信息，存储于 1 号扇区
typedef struct FS_ROOT
{
    U08     magic[32];          // 根目录名
    U32     rootBegin;          // 将根目录所涉及的所有扇区组织成一个链表，rootBegin 为第一个扇区号
    U32     rootNum;            // 根目录所占的扇区数 
    U32     lastBytes;          // 最后一个扇区中的有效数据字节数
} FS_ROOT;

/******************************************************************************
* 函数名称: E_RET FsFormat(void)
* 功能说明: 硬盘格式化
* 输入参数: 无
* 输出参数: 无
* 函数返回: E_OK:成功; E_ERR:失败
* 其它说明: 无
******************************************************************************/
static E_RET FsFormat(void)
{
    FS_HEADER* header = (FS_HEADER *)FS_MALLOC(SEC_SIZE);  // 申请一个扇区大小的内存用于文件概要信息（扇区 0）数据处理使用
    FS_ROOT* root = (FS_ROOT *)FS_MALLOC(SEC_SIZE);        // 申请一个扇区大小的内存用于根目录信息（扇区 1）数据处理使用
    U32* pUnit = (U32*)FS_MALLOC(SEC_SIZE);                // 申请一个扇区大小的内存用于扇区分配表数据处理
    U32 i = 0;
    U32 j = 0;
    U32 current = 0;

    if(NULL == header || NULL == root || NULL == pUnit)
        goto error;

    // 初始化扇区 0，即将文件系统概要信息写到扇区 0 中
    StrCpy(header->magic, FS_MAGIC, -1);
    header->sctNum = FS_SEC_NUM;
    // 计算扇区分配表所占扇区数
    // header->sctNum - 2 是因为扇区 0 和 扇区 1 被特定使用，这两个扇区需要先被踢除
    // + !!((header->sctNum - 2) % (SEC_SIZE / 4)) 是因为扇区分配表中最后一个扇区并不一定是满的。不足一个扇区也按一个扇区算
    header->mapNum = (header->sctNum - 2) / (SEC_SIZE / 4) + !!((header->sctNum - 2) % (SEC_SIZE / 4));
    // 格式化时第一个空闲的扇区号即为扇区分配表后的第一个扇区的扇区号
    header->freeBegin = 2 + header->mapNum;
    // 格式化时空闲扇区数等于总扇区数减去扇区 0 和 扇区 1 这两个固定的扇区，然后再减去扇区分配表所占的扇区数
    header->freeNum = header->sctNum - 2 - header->mapNum; 
    // 写到硬盘扇区 0 中
    if(E_ERR == FS_WRITE(0, (U08 *)header))
        goto error;

    // 初始化扇区 1，即将根目录相关信息写到扇区 1 中
    StrCpy(root->magic, ROOT_MAGIC, -1);
    // 格式化时根目录下为空，也可以看成是根目录这个文件没有数据，不占用一个扇区
    root->rootBegin = SEC_END_FLAG;
    root->rootNum = 0;
    root->lastBytes = 0;
    // 写到硬盘扇区 1 中
    if(E_ERR == FS_WRITE(1, (U08 *)root))
        goto error;

    // 初始化扇区分配表
    for(i = 0; (i < header->mapNum) && (current < header->freeNum); i++)
    {
        for(j = 0; j < MAP_UNIT_NUM; j++)
        {
            *(pUnit+j) = current + 1;
            current++;
            // 如果是最后一个扇区管理单元，则将其内数值设置为结束标志，并且跳出循环
            if(current == header->freeNum)
            {
                *(pUnit+j) = SEC_END_FLAG;
                break;
            }
        }
        // 写到硬盘扇区分配表中
        if(E_ERR == FS_WRITE(2+i, (U08 *)pUnit))
            goto error;
    }
    
    FS_FREE(header);
    FS_FREE(root);
    FS_FREE(pUnit);
    return E_OK;

error:
    FS_FREE(header);
    FS_FREE(root);
    FS_FREE(pUnit);
    return E_ERR;
}

/******************************************************************************
* 函数名称: E_RET FsIsFormatted(void)
* 功能说明: 判断硬盘是已经被格式化
* 输入参数: 无
* 输出参数: 无
* 函数返回: E_OK:成功; E_ERR:失败
* 其它说明: 如果硬盘未被格式化，则格式化硬盘，如果已经格式化过，则不再格式化
******************************************************************************/
#include <print.h>
E_RET FsIsFormatted(void)
{
    E_RET ret = E_OK;
    FS_HEADER* header = (FS_HEADER *)FS_Malloc(SEC_SIZE);  // 申请一个扇区大小的内存用于文件概要信息（扇区 0）数据处理使用

    if(NULL == header)
        return E_ERR;

    // 读取扇区 0 中的数据，比较 header->magic 中字符串是否等于 FS_MAGIC 
    // 如果相等，则证明硬盘已经被格式化过；如果不相等，则格式化硬盘
    if(E_ERR == FS_READ(0, (U08 *)header))
    {
        FS_FREE(header);
        return E_ERR;
    }

    if(FALSE == StrCmp(FS_MAGIC, header->magic, -1))
    {
        ret = FsFormat();
        printk("format:%d\n", ret);
    }

    FS_FREE(header);
    return ret;
}